package com.choosefine.paycenter.pay.service.impl;

import com.choosefine.paycenter.account.dto.OppositeDto;
import com.choosefine.paycenter.account.dto.OrderDto;
import com.choosefine.paycenter.account.model.Account;
import com.choosefine.paycenter.account.service.AccountBillService;
import com.choosefine.paycenter.account.service.AccountLockService;
import com.choosefine.paycenter.account.service.AccountService;
import com.choosefine.paycenter.common.constants.ExceptionCodeConstants;
import com.choosefine.paycenter.common.constants.ExceptionMessageConstants;
import com.choosefine.paycenter.common.enums.BizzSys;
import com.choosefine.paycenter.common.enums.PayStatus;
import com.choosefine.paycenter.common.exception.BusinessException;
import com.choosefine.paycenter.common.utils.MessageSourceUtils;
import com.choosefine.paycenter.common.utils.PasswordUtils;
import com.choosefine.paycenter.pay.dao.PaymentMapper;
import com.choosefine.paycenter.pay.service.PayVerifyService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * Comments：
 * Author：Jay Chang
 * Create Date：2017/3/24
 * Modified By：
 * Modified Date：
 * Why & What is modified：
 * Version：v1.0
 */
@Service
public class PayVerifyServiceImpl implements PayVerifyService {

    @Autowired
    private PaymentMapper paymentMapper;
    @Autowired
    private AccountService accountService;

    @Autowired
    private AccountBillService accountBillService;

    @Autowired
    private AccountLockService accountLockService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Value("${sys.pay.payPassRetryTimes}")
    private int payPassRetryTimes;

    @Value("${sys.pay.retryWaitTime}")
    private int retryWaitTime;

    @Autowired
    MessageSourceUtils localeMessageSourceUtils;

    private static final String ACCOUNT_KEY_PREFIX = "ACCT_PAY_PASS_INPUT_ERR_";

    /**
     * 校验交易主体账号是否存在，是否冻结
     * @param accountId
     */
    public void checkMainAccountExistAndIsBlocked(Long accountId){
        accountService.checkAccountExistAndIsBlocked(accountId);
    }

    /**
     * 校验交易对方是否存在
     * @param orderDtos
     */
    public void checkOppositeAccounts(List<OrderDto> orderDtos) {
        if(CollectionUtils.isEmpty(orderDtos)){
            throw new BusinessException(ExceptionCodeConstants.PAY_ACCOUNT_TRADEORDER_CANNOT_EMPTY,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAY_ACCOUNT_TRADEORDER_CANNOT_EMPTY));
        }
        for(OrderDto orderDto : orderDtos){
            accountService.checkAccountExist(orderDto.getUserCode());
        }
    }

    @Override
    public void checkOppositeAccountsByOppo(List<OrderDto> orderDtos) {
        if(CollectionUtils.isEmpty(orderDtos)){
            throw new BusinessException(ExceptionCodeConstants.PAY_ACCOUNT_TRADEORDER_CANNOT_EMPTY,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAY_ACCOUNT_TRADEORDER_CANNOT_EMPTY));
        }
        for(OrderDto orderDto : orderDtos){
            accountService.checkAccountExist(orderDto.getUserCode());
        }
    }

    //校验交易主体账户余额是否充足
    public void checkMainAccountBalanceEnough(Long accountId,BigDecimal amount){
        accountService.checkBalanceIsEnough(accountId,amount);
    }

    /**
     * 校验支付密码是否已经设置
     * @param mainAccount
     */
    public void  checkPayPassIsSet(Account mainAccount){
          if(StringUtils.isBlank(mainAccount.getPayPass())) {
              throw new BusinessException(ExceptionCodeConstants.ACCOUNT_CHECK_PAYPASSNOTSET,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.ACCOUNT_CHECK_PAYPASSNOTSET));
          }
    }

    /**
     * 校验请求中的业务流水号对应的支付单是否已经支付过了
     * @param bizzSys
     * @param bizzSn
     */
    public void checkPaymentExistsAndAlreadyPaid(BizzSys bizzSys,String bizzSn) {
        if(isPaymentExists(bizzSys,bizzSn)){
            throw new BusinessException(ExceptionCodeConstants.PAYMENT_BIZZSN_ALREADYEXISTS,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAYMENT_BIZZSN_ALREADYEXISTS,new Object[]{bizzSys,bizzSn}));
        }
        if(hasAlreadyPaid(bizzSys,bizzSn)){
            throw new BusinessException(ExceptionCodeConstants.PAYMENT_BIZZSN_ALREADYPAID,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAYMENT_BIZZSN_ALREADYPAID,new Object[]{bizzSys,bizzSn}));
        }
    }

    /**
     *  根据业务流水号查询对应的支付流水号
     * @param bizzSn
     * @return
     */
    public String findPaySnByBizzSn(String bizzSn){
        return paymentMapper.selectPaySnByBizzSn(bizzSn);
    }

    /**
     * 校验支付单是否已经创建
     * @param bizzSys
     * @param bizzSn
     * @return
     */
    public boolean isPaymentExists(BizzSys bizzSys, String bizzSn){
        return paymentMapper.selectPaymentCountByBizz(bizzSys,bizzSn) >= 1;
    }

    /**
     * 检查某个业务流水号对应的支付单是否已经支付过了
     * @return
     */
    public boolean hasAlreadyPaid(BizzSys bizzSys, String bizzSn){
        return paymentMapper.selectPaidCountByBizzSn(bizzSys,bizzSn, PayStatus.PAID) >= 1;
    }

    /**
     * 校验支付密码是否正确
     */
    public void checkPayPass(Long accountId,String payPass){
        Account account = accountService.findById(accountId);
        //校验支付密码是否已经设置
        checkPayPassIsSet(account);
        ValueOperations<String, String> stringValueOperations = redisTemplate.opsForValue();
        String key = ACCOUNT_KEY_PREFIX+account.getId();
        String value = stringValueOperations.get(key);
        if(!account.getPayPass().equals(PasswordUtils.generateSecurityPayPass(payPass,account.getSalt()))){
            if(Objects.isNull(value)){
                stringValueOperations.set(key,Integer.toString(1));
            }else{
                stringValueOperations.set(key,(Integer.valueOf(value)+1)+"");
            }
            int nowInputErrorTimes = Integer.valueOf(stringValueOperations.get(key));
            redisTemplate.expire(key,retryWaitTime,TimeUnit.MINUTES);
            if(nowInputErrorTimes >= payPassRetryTimes){
                final String lockKey=AccountService.ACCOUNT_LOCK_PREFIX+account.getId();
                long seconds = 0L;
                long minutes = 10L;
                if (nowInputErrorTimes == payPassRetryTimes){
                    //锁定账户10分钟
                    accountLockService.lockAccount(account.getId());
                    throw new BusinessException(ExceptionCodeConstants.PAY_CHECK_PAYPASSTIMEERROR, localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAY_CHECK_PAYPASSTIMEERROR, new Object[]{"10", "00"}));
                }else{
                    seconds = (Long) redisTemplate.execute(new RedisCallback() {
                        @Override
                        public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                            return redisConnection.ttl(lockKey.getBytes());
                        }
                    });
                    minutes = seconds / 60;
                    seconds = seconds % 60;
                    //输入支付密码错误过多账户已被锁定，请点击忘记密码进行找回或9分钟26秒后重试
                    throw new BusinessException(ExceptionCodeConstants.PAY_CHECK_PAYPASSTIMEERROR, localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAY_CHECK_PAYPASSTIMEERROR, new Object[]{String.format("%02d",minutes), String.format("%02d",seconds)}));
                }
            }else{
                //支付密码输入不正确，您还有{0}次输入机会
                throw new BusinessException(ExceptionCodeConstants.ACCOUNT_CHECK_PAYPASSERROR,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.ACCOUNT_CHECK_PAYPASSERROR,new Object[]{payPassRetryTimes-nowInputErrorTimes}));
            }
        }else{
            redisTemplate.delete(key);
        }
    }

    /**
     *  校验是否已经支付
     * @param paySn
     * @param bizzSys
     * @param bizzSn
     */
    public void checkHasAlreadyPaid(String paySn,BizzSys bizzSys,String bizzSn) {
        PayStatus payStatus = paymentMapper.selectStatusByPaySn(paySn);
        if(PayStatus.PAID.equals(payStatus)){
            throw new BusinessException(ExceptionCodeConstants.PAYMENT_BIZZSN_ALREADYPAID,localeMessageSourceUtils.getMessage(ExceptionMessageConstants.PAYMENT_BIZZSN_ALREADYPAID,new Object[]{bizzSys,bizzSn}));
        }
    }
}
